import java.awt.Frame;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.io.IOException;
import java.util.ArrayList;

import javax.media.opengl.*;
import javax.media.opengl.glu.GLU;
import javax.swing.JFrame;
import javax.vecmath.Point3f;

import com.sun.opengl.util.FPSAnimator;

/**
 * 
 * @author Eric Muehlberg
 *
 *Contains the main loop of the program
 */
public class Engine implements GLEventListener, KeyListener, MouseListener, MouseMotionListener, ActionListener
{
	GLU glu = new GLU();
	GL gl;
	GLCanvas canvas;
	FPSAnimator animator;
	player p;
	timer t;
	Map level;
	Camera camera;
	
	float xpos = 0, ypos = 0, zpos = 0;
	float centerx, centery, centerz;
	float roth = 0, rotv = 0;
	
	float znear = 0.01f; 
	float zfar = 1000.0f;
	
	int mouseX, mouseY, mouseButton;
	int keyButton;
	int wrotationSegments;
	float motionSpeed, wrotateSpeed;
	float animation_speed = 1.0f;
	int winW;
	int winH;
	boolean quit;
	boolean blockUnder = false;
	WObject underblock;
	
	boolean keypressed = false;
	/**
	 * 
	 * @author Eric Muehlberg
	 *
	 *	Handles everything camera related
	 *
	 *	-Needs to be implemented properly
	 *	-Leave for later
	 */
	public class Camera
	{
		float posx ,posy, posz;
		float centerx, centery, centerz;
		float roth, rotv;
		boolean follow;
		WObject followobj;
		
		public Camera()
		{
			centerx = 0;
			centery = 0;
			centerz = 0;
			posx = centerx;
			posy = centery;
			posz = centerz + 500;
			roth = 0;
			rotv = 0;
			follow = false;
			followobj = null;
		}
		
		/**
		 * Snap the camera right to follow the worlds leftward rotation
		 */
		public void snapleft()
		{
			
		}
		
		/**
		 * Snap the camera left to follow the worlds rightward rotation
		 */
		public void snapright()
		{
			
		}
		
		/**
		 * Follow The specified object
		 * @param f
		 * @param w
		 */
		public void setfollow(boolean f, WObject w)
		{
			follow = f;
			if(f)
				followobj = w;
		}
		
		/**
		 * Update the camera settings
		 */
		public void update()
		{
			if(follow)
			{
				/*this.posx = followobj.cube.pos.x;
				this.posy = followobj.cube.pos.y;
				this.posz = followobj.cube.pos.z;*/
			}
		}
	}
	
	public Engine()
	{
		JFrame frame = new JFrame("473 Final");
		t = new timer(); 
		t.start();
		p = new player(new ObjectThree(0,1,0,1,1,1,null,"cube.obj"));
		
		//Init everything
		try {
			level = new Map("test.level");
		} catch (IOException e) {
			System.out.println("level could not be loaded");
		}
		
		quit = false;
		
		canvas = new GLCanvas();
		canvas.addGLEventListener(this);
		canvas.addMouseListener(this);
		canvas.addMouseMotionListener(this);
		canvas.addKeyListener(this);
		animator = new FPSAnimator(canvas, 30);
		frame.getContentPane().add(canvas);
		frame.setSize(1028,1028);
		frame.setLocationRelativeTo(null);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
		animator.start();
		canvas.requestFocus();
		underblock = new WObject(level.mapobjects.get(0)); //assumes that the first block in the level is the on that the player starts on.
		t.start();
		float dy = 0;
		float dx = 0;
		boolean colbot = false;
		
		while(!quit)
		{		

			t.start();
			
			//Check collisions
			for(WObject w: level.mapobjects)
			{
				PrecisionCheck(p.obj, w);


//				if(CollisionCheck(p.obj, w))
//				{
//					CollisionFace coll = PrecisionCheck(p.obj, w);
//					
//					if(coll.getLeft())
//					{
//						
//					}
//					
//					if(coll.getRight())
//					{
//						dx = p.getX() - p.getW();
//						p.updateX(dx);
//					}
//					
//					if(coll.getTop())
//					{
//						
//					}
//					
//					if(coll.getBottom())
//					{
//						p.updateDY(0);
//						colbot = true;
//						p.canjump = true;
//						p.jumping = false;
//						//Bump player to frontmost cube
//						if(w.getRZ() > p.getRZ())
//						{
//							p.updateRZ(w.getRZ());
//						}
//					}
//					
//				}
			}
			p.update(t.getTime());
			blockUnder = false;
//			if(underblock.getRZ() != p.getRZ())
//			{
//				p.updateRZ(underblock.getRZ());
//			}

			//Update display
			canvas.display();		
		}
	}
	
	public static void main(String[] args)
	{
		new Engine();
	}

	public void display(GLAutoDrawable drawable)
	{
		gl = drawable.getGL();
		gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
		
		gl.glPolygonMode(GL.GL_FRONT_AND_BACK, GL.GL_FILL);
		gl.glShadeModel(GL.GL_SMOOTH);
		gl.glEnable(GL.GL_CULL_FACE);
		
		gl.glLoadIdentity();
		gl.glTranslatef(-xpos, -ypos, -zpos);
		
		//unnecessary?
		//gl.glTranslatef(centerx, centery, centerz);
		
		//Rotates scene
		gl.glRotatef(360.f - roth, 0, 1.0f, 0);
		
		//Not needed for gameplay but keep for demonstration use
		gl.glRotatef(rotv, 1.0f, 0, 0);

		gl.glTranslatef(-centerx, -centery, -centerz);
		
		for(WObject w: level.mapobjects)
		{	
			gl.glPushMatrix();
			if(((ObjectThree)w).m != null)
			{
				gl.glTranslatef(w.getX(), w.getY(), w.getZ());
			
				((ObjectThree)w).m.Draw(gl, glu);
			}
			gl.glPopMatrix();
		}
		
		gl.glPushMatrix();
		gl.glTranslatef(p.getX(), p.getY(), p.getZ());
		((ObjectThree)p.obj).m.Draw(gl, glu);
		gl.glPopMatrix();
		

		
	}
	
	void initViewParameters()
	{
		roth = rotv = 0;

		centerx = 0;
		centery = 0;
		centerz = 0;
		xpos = centerx;
		ypos = centery;
		zpos = centerz + 500;
		
		camera = new Camera();

		motionSpeed = 1f;
		
		//World rotation
		wrotateSpeed = 0.01f;
		wrotationSegments = 100;

	}	
	
	public void mousePressed(MouseEvent e) {	
		mouseX = e.getX();
		mouseY = e.getY();
		mouseButton = e.getButton();
		canvas.display();
	}
	
	public void mouseReleased(MouseEvent e) {
		mouseButton = MouseEvent.NOBUTTON;
		canvas.display();
	}	
	
	public void mouseDragged(MouseEvent e) {
		int x = e.getX();
		int y = e.getY();
		if (mouseButton == MouseEvent.BUTTON3) {
			zpos -= (y - mouseY) * motionSpeed;
			mouseX = x;
			mouseY = y;
			canvas.display();
		} else if (mouseButton == MouseEvent.BUTTON2) {
			xpos -= (x - mouseX) * motionSpeed;
			ypos += (y - mouseY) * motionSpeed;
			mouseX = x;
			mouseY = y;
			canvas.display();
		} else if (mouseButton == MouseEvent.BUTTON1) {
			roth -= (x - mouseX) * wrotateSpeed;
			rotv += (y - mouseY) * wrotateSpeed;
			mouseX = x;
			mouseY = y;
			canvas.display();
		}
	}

	public void displayChanged(GLAutoDrawable arg0, boolean arg1, boolean arg2)
	{
		// TODO Auto-generated method stub
	}

	//This needs to be changed a bit
	public void init(GLAutoDrawable gld)
	{
		gl = gld.getGL();
		initViewParameters();
		gl.glClearColor(0.1f, 0.1f, 0.1f, 1f);
		gl.glClearDepth(1.0f);
		
		 // white light at the eye
	    float light0_position[] = { 0, 0, 1, 0 };
	    float light0_diffuse[] = { 1, 1, 1, 1 };
	    float light0_specular[] = { 1, 1, 1, 1 };
	    gl.glLightfv( GL.GL_LIGHT0, GL.GL_POSITION, light0_position, 0);
	    gl.glLightfv( GL.GL_LIGHT0, GL.GL_DIFFUSE, light0_diffuse, 0);
	    gl.glLightfv( GL.GL_LIGHT0, GL.GL_SPECULAR, light0_specular, 0);
	    
	  //material
	    float mat_ambient[] = { 0, 0, 0, 1 };
	    float mat_specular[] = { .8f, .8f, .8f, 1 };
	    float mat_diffuse[] = { .4f, .4f, .4f, 1 };
	    float mat_shininess[] = { 128 };
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_AMBIENT, mat_ambient, 0);
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_SPECULAR, mat_specular, 0);
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_DIFFUSE, mat_diffuse, 0);
	    gl.glMaterialfv( GL.GL_FRONT, GL.GL_SHININESS, mat_shininess, 0);

	    float bmat_ambient[] = { 0, 0, 0, 1 };
	    float bmat_specular[] = { 0, .8f, .8f, 1 };
	    float bmat_diffuse[] = { 0, .4f, .4f, 1 };
	    float bmat_shininess[] = { 128 };
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_AMBIENT, bmat_ambient, 0);
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_SPECULAR, bmat_specular, 0);
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_DIFFUSE, bmat_diffuse, 0);
	    gl.glMaterialfv( GL.GL_BACK, GL.GL_SHININESS, bmat_shininess, 0);

	    float lmodel_ambient[] = { 0, 0, 0, 1 };
	    gl.glLightModelfv( GL.GL_LIGHT_MODEL_AMBIENT, lmodel_ambient, 0);
	    gl.glLightModeli( GL.GL_LIGHT_MODEL_TWO_SIDE, 1 );

	    gl.glEnable( GL.GL_NORMALIZE );
	    gl.glEnable( GL.GL_LIGHTING );
	    gl.glEnable( GL.GL_LIGHT0 );
	    
		gl.glEnable(GL.GL_DEPTH_TEST);
		gl.glDepthFunc(GL.GL_LEQUAL);
		//gl.glHint(GL.GL_PERSPECTIVE_CORRECTION_HINT, GL.GL_NICEST);
		gl.glCullFace(GL.GL_BACK);
		gl.glEnable(GL.GL_CULL_FACE);
		gl.glShadeModel(GL.GL_SMOOTH);	
	}
	
	public void reshape(GLAutoDrawable drawable, int x, int y, int width, int height)
	{
		winW = width;
		winH = height;

		gl.glViewport(0, 0, width, height);
		
		gl.glMatrixMode(GL.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrtho(centerx-10, 10, centery-10, 10, znear, zfar);
		
		//Perspective related but not needed
		//Keep for potential future use
		//glu.gluPerspective(25.f, (float)width/(float)height, znear, zfar);
		
		gl.glMatrixMode(GL.GL_MODELVIEW);
		gl.glLoadIdentity();
	}
	
	/**
	 * Does a simple check to see if two objects are colliding.
	 *
	 *	-Need to write a method for more precise check for collision handling
	 *	-Do we want complex collision checking for precision? or just to handle side detection
	 *
	 *	We need at the least, bottom and sides  -nick
	 * 
	 * @param w1
	 * @param w2
	 * @return
	 */
	public boolean CollisionCheck(WObject w1, WObject w2)
	{
		
		if(w1 == null || w2 == null)
		{
			//Print message in case this wasn't supposed to occur
			System.out.println("At least one object null");
			return false;
		}
		else
		{
			//Left of w1 >= right of w2
			if((w1.getRX() - (w1.getRW()/2)) >= (w2.getRX() + (w2.getRW()/2)))
			{
				return false;
			}
			//Right of w1 <= left of w2
			else if((w1.getRX() + (w1.getRW()/2)) <= (w2.getRX() - (w2.getRW()/2)))
			{
				return false;
			}
			//Bottom of w1 >= top of w2
			else if((w1.getY() - (w1.getH()/2)) >= (w2.getY() + (w2.getH()/2)))
			{
				return false;
			}
			//Top of w1 <= top of w2
			else if((w1.getY() + (w1.getH()/2)) <= (w2.getY() - (w2.getH()/2)))
			{
				return false;
			}
			
			//If none of the above apply a collision has occurred
			return true;

		}
	}
	
	/**
	 * Precision Collision Check
	 */
	public CollisionFace PrecisionCheck(WObject w1, WObject w2)
	{

		ObjectFace LeftA, LeftB;
		ObjectFace RightA, RightB;
		ObjectFace TopA, TopB;
		ObjectFace BottomA, BottomB;
		
		CollisionFace result = new CollisionFace();

		
		Orientation o = w1.o; 
		
		BottomA = w1.getBottom();
		TopA = w1.getBottom();
		
		BottomB = w2.getBottom();
		TopB = w2.getTop();
		
		LeftA = w1.getLeft();
		RightA = w1.getRight();
		LeftB = w2.getLeft();
		RightB = w2.getRight();
		
		switch (w1.o)
		{
			case xAxis:
				break;
			case nxAxis:
				LeftA = w1.getRight();
				RightA = w1.getLeft();
				LeftB = w2.getRight();
				RightB = w2.getLeft();
				break;
			case zAxis:
				LeftA = w1.getFront();
				RightA = w1.getBack();
				LeftB = w2.getFront();
				RightB = w2.getBack();
				break;
			case nzAxis:
				LeftA = w1.getBack();
				RightA = w1.getFront();
				LeftB = w2.getBack();
				RightB = w2.getFront();
				break;
			
		}
		
		//TopA collision BottomB
			//setTop

		//TopB collision BottomA
			//setBottom
		if( bottomCollision(w1, w2))
		{
			blockUnder = true;
			p.falling = false;
			if(p.getRZ() <= w2.getRZ())
			{
				p.updateRZ(w2.getRZ());
			}
		}
		else if(! blockUnder)
			p.falling = true;

		//LeftA collision RightB
			//setLeft
		if(leftSideCollides(w1, w2))
		{
			p.moveleft = false;	
		}
		
		//RightA collision LeftB
			//setRight
		if(rightSideCollides(w1, w2))//collides(RightA, LeftB, w1, w2))
		{
			p.moveright = false;
		}
		
		return result;
	}
	
	
	private boolean bottomCollision(WObject w1, WObject w2) {
		double fudgefactor = .08;
		
		float bottomYW1 = w1.pos.y - w1.h/2;
		float topYW2 = w2.pos.y + w2.h/2;
		
		if(Math.abs(w1.getRX() - w2.getRX()) >= 1)
			return false;
		if(bottomYW1 > 0f && topYW2 < 0f)
			return false;
		else if(bottomYW1 == 0f && topYW2 == 0f)
			return true;
		else if(bottomYW1 - topYW2 < fudgefactor && ! (Math.abs(bottomYW1 -  topYW2) >= w1.h + w2.h))
			return true;
		else 
			return false;
	}

	private boolean leftSideCollides(WObject w1, WObject w2) {
		double fudgefactor = .08;
		if(Math.abs(w1.pos.y - w2.pos.y) >= 1 )
			return false;
		
		float leftWallXW1 = (w1.getRX() - w1.getRW()/2);
		float rightWallXW2 =(w2.getRX() + w2.getRW()/2);
		
		if(leftWallXW1 > 0f && rightWallXW2 < 0f)
			return false;
		else if(leftWallXW1 == 0f && rightWallXW2 == 0f)
			return true;
		else if(leftWallXW1 - rightWallXW2 < fudgefactor && ! (Math.abs(leftWallXW1 - rightWallXW2) >= w1.getRW() + w2.getRW()))
			return true;
		else 
			return false;
			
	}

	private boolean rightSideCollides(WObject w1, WObject w2) {
		double fudgefactor = -.08;
		if(Math.abs(w1.pos.y - w2.pos.y) >= 1 )
			return false;
		
		float rightWallXW1 = (w1.getRX() + w1.getRW()/2);
		float leftWallXW2 =(w2.getRX() - w2.getRW()/2);
		
		if(rightWallXW1 > 0f && leftWallXW2 < 0f)
			return false;
		else if(rightWallXW1 == 0f && leftWallXW2 == 0f)
			return true;
		else if(rightWallXW1 - leftWallXW2 > fudgefactor && ! (Math.abs(rightWallXW1 - leftWallXW2 ) >= w1.getRW() + w2.getRW() ))
			return true;

		else 
			return false;
	}


	



	//method for executing player jumps
	private void jump()
	{
		p.jump();
	}
	
	//rotates the image to the left 90 degrees
	private void rotateRight()
	{
		int delayTime = 3;
		stopProjection();
		for(int i = 0; i < wrotationSegments; i++)
		{
			roth += 90*wrotateSpeed;
			canvas.display();
			try {
				Thread.sleep(delayTime);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}

	//rotates the image to the left 90 degrees
	private void rotateLeft()
	{
		int delayTime = 3;
		stopProjection();
		for(int i = 0; i < wrotationSegments; i++)
		{
			roth -= 90*wrotateSpeed;
			canvas.display();
			try {
				Thread.sleep(delayTime);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
	}
	
	//stops the 2d projections of the 3d screen while the screen rotates
	private void stopProjection() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseClicked(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseEntered(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseExited(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void mouseMoved(MouseEvent e) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * Handles perspective shifts to the right
	 */
	public void snapRight()
	{
		p.snapRight();
		for(WObject w: level.mapobjects)
		{
			((Cube)w).snapright();
		}
		rotateRight();
		for(WObject w: level.mapobjects)
		{
			//Smart collision check to see if any object is in in the same xy coordinates
			if(CollisionCheck(w, p.obj))
			{
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//Assume that you will never get stuck in an infinite loop
				snapLeft();
				break;
			}
			
		}
	}
	
	/**
	 * handles perspective shifts to the right
	 */
	public void snapLeft()
	{
		p.snapLeft();
		for(WObject w: level.mapobjects)
		{
			((Cube)w).snapleft();
		}
		rotateLeft();
		for(WObject w: level.mapobjects)
		{
			//Smart collision check to see if any object is in in the same xy coordinates
			if(CollisionCheck(w, p.obj))
			{
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				//Assume that you will never get stuck in an infinite loop
				snapRight();
				break;
			}
			
		}
	}
	
	@Override
	public void keyTyped(KeyEvent e)
	{

		switch (keyButton)
		{
			case KeyEvent.VK_Q:
				snapLeft();
				break;
			case KeyEvent.VK_E:
				snapRight();
				break;
			case KeyEvent.VK_W:
				jump();
				break;
		}
	}



	@Override
	public void keyPressed(KeyEvent e)
	{
		keyButton = e.getKeyCode();
		canvas.display();

		switch (keyButton)
		{
			case KeyEvent.VK_A:
				p.moveleft = true;
				
				break;
			case KeyEvent.VK_D:
				p.moveright = true;
				
				break;
			case KeyEvent.VK_F:
				camera.setfollow(!camera.follow, p.obj);
				break;
		}
		
//		for(WObject w: level.mapobjects)
//			PrecisionCheck(p.obj, w);

		//p.update();
		
	
	}

	@Override
	public void keyReleased(KeyEvent e)
	{
		keyButton = e.getKeyCode();
		canvas.display();

		switch (keyButton)
		{
			case KeyEvent.VK_A:
				p.moveleft = false;
				
				break;
			case KeyEvent.VK_D:
				p.moveright = false;
				
				break;
		}

		//p.update();
		
		//keyButton = KeyEvent.VK_UNDEFINED;
		//canvas.display();
	}
	
	

}
